/**************************************************************************************************
* Title: SimulationManager.cpp
* Authors: Gael Huber
* Description: Manages the actual game
**************************************************************************************************/
#include "SimulationManager.h"

// Constructor
SimulationManager::SimulationManager(void) {
}	

// Destructor
SimulationManager::~SimulationManager(void) {
}

// Initialize
void SimulationManager::startUp(void) {
	// random seed
	srand((unsigned int) time(0));
	
	// initialize variables
	playerScore = 0;
	aiScore = 0;
	aiSpeed = 1;
	aiDirection = 1;
	ballVelocity = Vector2(0,0);

	// Player paddle
	player = RenderManager::getSingleton().objectList[0];
	player.screenX = 50;
	player.screenY = (float) getWindowHeight() / 2.0f;
	player.width = 10;
	player.halfWidth = 5.0f;
	player.height = 50;
	player.halfHeight = 25.0f;
	RenderManager::getSingleton().addToScene(&player);

	// AI paddle
	ai = RenderManager::getSingleton().objectList[0];
	ai.screenX = (float) getWindowWidth() - 50.0f;
	ai.screenY = (float) getWindowHeight() / 2.0f;
	ai.width = 10;
	ai.halfWidth = 5.0f;
	ai.height = 50;
	ai.halfHeight = 25.0f;
	RenderManager::getSingleton().addToScene(&ai);

	// Ball
	ball = RenderManager::getSingleton().objectList[1];
	ball.screenX = (float) getWindowWidth() / 2.0f;
	ball.screenY = (float) getWindowHeight() / 2.0f;
	ball.width = 10;
	ball.halfWidth = 5.0f;
	ball.height = 10;
	ball.halfHeight = 5.0f;
	RenderManager::getSingleton().addToScene(&ball);

	// Input Manager
	inputMgr = new InputManager();

	// Rotate the paddles by the following value
	paddleRotatePlayer = 1.0f;
	paddleRotateAI = 1.0f;
	setPaddleBounds();
}

// Shutdown
void SimulationManager::shutDown(void) {
}

// Update
void SimulationManager::gameLoop(void) {
	MSG msg;	// Windows message
	BOOL done = FALSE;	// Bool variable to exit loop

	while( !done ) {
		// Is there a message waiting?
		if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) {
			// Have we received a quit message?
			if( msg.message == WM_QUIT ) {
				done = TRUE;	// If so, we want to quit
			} else {
				// Otherwise deal with Windows messages
				TranslateMessage(&msg);	// Translate the message
				DispatchMessage(&msg);	// Dispatch the message
			}
		} else { // If there is no message
			// Handle keyboard input
			inputMgr->handleInput(&player, &ballVelocity);
			
			// Draw the scene. Watch for ESC key and quit message from DrawGLScene()
			// Program active?
			if( getWindowFocus() ) {
				// Wiggle the player and ai
				if((player.angle >= playerBounds && paddleRotatePlayer > 0) || 
					(player.angle <= -playerBounds && paddleRotatePlayer < 0))
					paddleRotatePlayer *= -1.0f;
				if((ai.angle >= aiBounds && paddleRotateAI > 0) || 
					(ai.angle <= -aiBounds && paddleRotateAI < 0))
					paddleRotateAI *= -1.0f;
				player.angle += paddleRotatePlayer;
				ai.angle += paddleRotateAI;
				

				// Ensure the objects are within bounds and move them
				checkBounds(&player);
				moveBall();
				moveAI();

				// Check for collisions
				physics();

				// Rendering
				RenderManager::getSingleton().createScoreToPrint(playerScore, aiScore);		// give the score to the render manager
				RenderManager::getSingleton().renderScene();	// Draw the scene
			}
		}
	}
}

// Check against the bounds of the window
int SimulationManager::checkBounds(RenderObject* obj) {
	// check against the top:
	if((obj->screenY + obj->halfHeight) > getWindowHeight()) {		// bottom
		// put the object fully on the screen
		obj->screenY = getWindowHeight() - obj->halfHeight;
		return -1;
	}

	if((obj->screenY - obj->halfHeight) < 0) {		// top
		// put the object fully on the screen
		obj->screenY = obj->halfHeight;
		return 1;
	}

	// check against the sides (only needed for the ball)
	if((obj->screenX + obj->halfWidth) > getWindowWidth()) {	// ai side
		// don't do anything but return that there was a collsion
		return -2;
	}

	if((obj->screenX - obj->halfWidth) < 0) {		// player side
		// don't do anything but return that there was a collision
		return 2;
	}
	return 0;
}

// Check for collision
bool SimulationManager::checkCollision(RenderObject a) {
	float angle = a.angle * 3.14159265f / 180.0f;
	float sin = std::sin(-angle);	// Sin
	float cos = std::cos(-angle);	// Cos

	// Rotate the ball into the correct frame of reference
	float x = (ball.screenX - a.screenX) * cos - (ball.screenY - a.screenY) * sin;
	float y = (ball.screenY - a.screenY) * cos + (ball.screenX - a.screenX) * sin;

	// Overlaps
	float xOverlap = x;
	float yOverlap = y;
	float halfWidth = ball.halfWidth + a.halfWidth;
	float halfHeight = ball.halfHeight + a.halfHeight;

	// If we are not within 100, don't even bother checking
	if(std::sqrt(x*x + y*y) > 100.0f)
		return false;

	// Is there an x overlap?
	if(abs(xOverlap) <= halfWidth) {
		// Is there a y overlap?
 		if(abs(yOverlap) <= halfHeight) {
			// Ball's velocity in local orientation
			Vector2 vel = Vector2(ballVelocity.x * cos - ballVelocity.y * sin, ballVelocity.y * cos + ballVelocity.x * sin);
			float speedUp = 0.1f;	// Factor by which to accelerate the ball

			// There must be a collision, so we will react
			if(xOverlap - halfWidth >= yOverlap - halfHeight) {
				// Left/right collision
				if(x * vel.x < 0) {
					vel.x *= -1.0f;
					vel.y *= 1.0f;
				} else {
					// Move the ball accordingly
					ball.screenX += -(abs(xOverlap) - halfWidth);
				}
			} else if(yOverlap - halfHeight > xOverlap - halfWidth) {
				// Top/bottom collision
				if(y * vel.y < 0) {
					vel.x *= 1.0f;
					vel.y *= -1.0f;
				} else {
					// Move the ball accordingly
					ball.screenY += -(abs(yOverlap) - halfHeight);
				}
			} else {
				// Corner
				if(x * vel.x < 0) {
					vel.x *= -1.0f;
				} else {
					// Move the ball accordingly
					ball.screenX += -(abs(xOverlap) - halfWidth);
				}

				if(y * vel.y < 0) {
					vel.y *= -1.0f;
				} else {
					// Move the ball accordingly
					ball.screenY += -(abs(yOverlap) - halfHeight);
				}
			}

			// Increase the speed in the x direction
			if(vel.x < 0.0f)
				vel.x -= speedUp;
			else
				vel.x += speedUp;
			
			// Increase the speed in the y direction
			if(vel.y < 0.0f)
				vel.y -= speedUp;
			else
				vel.y += speedUp;

			// Rotate back into original frame of reference
			sin = std::sin(angle);	// Sin
			cos = std::cos(angle);	// Cos
			ballVelocity = Vector2(vel.x * cos - vel.y * sin, vel.y * cos + vel.x * sin);

			// Reset paddle wiggle bounds
			setPaddleBounds();

			return true;
		}
	}
	return false;
}

// moves the ai :)
void SimulationManager::moveAI(void) {
	// check if we change direction due to hitting the wall
	int change = checkBounds(&ai);
	if(change != 0)
		aiDirection = change;
	
	// should only move if the ball is heading toward us
	if(ballVelocity.x > 0) {
		float dist = ball.screenY - ai.screenY;
		if(abs(dist) > 5) {
			// set the direction
			if(dist > 0) 
				aiDirection = 1;
			else
				aiDirection = -1;
			// set the new speed of the paddle
			setSpeed();
			// update the screen coordinates
			ai.screenY += aiDirection * aiSpeed;
		}
	}
}

// sets the speed of the paddle of the ai
void SimulationManager::setSpeed(void) {
	// mess with the speed of the paddle so it's more fair
	if(ballVelocity.magnitude() > 0) {
		float baselineVel = ballVelocity.magnitude() * .90f;
		float varient = rand()%((int)(ballVelocity.magnitude() * 20.0f)) - ballVelocity.magnitude() * 10.0f;
		aiSpeed = baselineVel + (varient/100);
	}
}



void SimulationManager::moveBall(void) { 
	// check collisions
	int change = checkBounds(&ball);
	if(change != 0) {
		if(change == -2 || change == 2) {	// the ball collided with the sides
			// set the velocity to 0
			ballVelocity = Vector2(0, 0); 
			// give the player or ai a point
			if(change == 2)
				aiScore += 1;
			else
				playerScore += 1;
			// return the ball to the center
			ball.screenX = (float) getWindowWidth() / 2.0f;
			ball.screenY = (float) getWindowHeight() / 2.0f;
		} else {
			// invert the y to have it bounce
			ballVelocity = Vector2(ballVelocity.x, -ballVelocity.y);
		}
	}

	// move the ball
 	ball.screenX += ballVelocity.x;
	ball.screenY += ballVelocity.y;
}


void SimulationManager::setPaddleBounds(void) {
	int max = 40;
	int min = 10;
	int diff = max - min;
	srand((unsigned int)time(0));
	
	// Player
	playerBounds = (float) (rand() % (max - min) + min);

	// AI
	aiBounds = (float) (rand() % (max - min) + min);
}

// Check for collisions and general physics
void SimulationManager::physics(void) {
	// Intermediate velocity for ball, used for checking steps
	Vector2 intermediateVelocity = ballVelocity;

	// If the intermediate velocity's magnitude is more than a halfwidth, multisample
	if(intermediateVelocity.magnitude() > ball.halfWidth / 2) {
		// Determine the vector by which to shift during multisampling
		Vector2 shiftBy = intermediateVelocity.getNormalized() * (ball.halfWidth / 2);
		bool hit = false;	// Did we hit anything?

		// First sample
		hit |= checkCollision(player);
		hit |= checkCollision(ai);

		// Return if we hit anything
		if(hit == true)
			return;
		
		// Shift the velocity
		intermediateVelocity -= shiftBy;

		// Adjust the ball's position
		ball.screenX += shiftBy.x;
		ball.screenY += shiftBy.y;

		// Multisample
		while(intermediateVelocity.magnitude() > ball.halfWidth) {
			// First sample
			hit |= checkCollision(player);
			hit |= checkCollision(ai);

			// Return if we hit anything
			if(hit == true)
				return;
		
			// Shift the velocity
			intermediateVelocity -= shiftBy;

			// Adjust the ball's position
			ball.screenX += shiftBy.x;
			ball.screenY += shiftBy.y;
		}
	} else {
		// No multisampling
		checkCollision(player);
		checkCollision(ai);
	}
}